//
//  DownloadManager.m
//  DownloadManagerDemo
//
//  Created by ejiang on 2021/12/9.
//

#import "DownloadManager.h"
#import "DownloadTask.h"


@interface DownloadManager ()<NSURLSessionDownloadDelegate>

@property (nonatomic, strong) NSMutableArray <DownloadTask *>* dataSource;

@property (nonatomic, strong) NSURLSession * session;
@property (nonatomic, strong) NSMutableArray <NSURLSessionDownloadTask *>* downloadTasks;

@end

@implementation DownloadManager

+ (instancetype)sharedDownload {
    static DownloadManager * manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[DownloadManager alloc] init];
    });
    return manager;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _maxDownloadNum = 1;
        NSString * bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
        NSString * identifier = [bundleId stringByAppendingString:@".Download"];
        NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
        configuration.allowsCellularAccess = 1;
        _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    }
    return self;
}

- (void)startAll {
    for (int i = 0; i < _maxDownloadNum; i ++) {
        [self _startNextTask];
    }
}

- (void)pauseAll {
    if ([self.dataSource count] == 0 && [self.downloadTasks count] == 0) {
        return;
    }
    for (NSURLSessionDownloadTask * obj in self.downloadTasks) {
        // 会保留resume data
        __weak typeof(self) weak_self = self;
        [obj cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
            [weak_self _saveResumeData:resumeData taskDescription:obj.taskDescription];
        }];
    }
    [self.downloadTasks removeAllObjects];

    for (DownloadTask * obj in self.dataSource) {
        if (obj.status != DownloadTaskStatusError && obj.status != DownloadTaskStatusFinish) {
            obj.speed.oldDate = nil;
            obj.status = DownloadTaskStatusPause;
            [obj updateToDB];
        }
    }
    if ([self.delegate respondsToSelector:@selector(downloadManagerDidPaused:)]) {
        [self.delegate downloadManagerDidPaused:nil];
    }
}

- (void)resumeAll {
    if ([self.dataSource count] == 0 && [self.downloadTasks count] == 0) {
        return;
    }

    for (DownloadTask * obj in self.dataSource) {
        if (obj.status == DownloadTaskStatusPause) {
            obj.status = DownloadTaskStatusUnknown;
            [obj updateToDB];
        }
    }
    
    if ([self.delegate respondsToSelector:@selector(downloadManagerDidResume:)]) {
        [self.delegate downloadManagerDidResume:nil];
    }
    
    [self _startNextTask];
}

- (void)pause:(DownloadTask *)task {
    if ([self.dataSource count] == 0 && [self.downloadTasks count] == 0) {
        return;
    }
    if (task == nil) {
        return;
    }

    task.status = DownloadTaskStatusPause;
    [task updateToDB];
    
    NSURLSessionDownloadTask * sessionTask = nil;
    for (NSURLSessionDownloadTask * obj in self.downloadTasks) {
        if ([task.taskId isEqualToString:obj.taskDescription]) {
            sessionTask = obj;
            break;
        }
    }
    if (sessionTask) {
        // 会保留 resume data
        __weak typeof(self) weak_self = self;
        [sessionTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
            [weak_self _saveResumeData:resumeData taskDescription:sessionTask.taskDescription];
        }];
        [self.downloadTasks removeObject:sessionTask];
    }
    
    if ([self.delegate respondsToSelector:@selector(downloadManagerDidPaused:)]) {
        [self.delegate downloadManagerDidPaused:task];
    }
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self _startNextTask];
    });
}

- (void)resume:(DownloadTask *)task {
    if ([self.dataSource count] == 0 && [self.downloadTasks count] == 0) {
        return;
    }
    if (task == nil) {
        return;
    }

    task.status = DownloadTaskStatusUnknown;
    [task updateToDB];
    
    if ([self.delegate respondsToSelector:@selector(downloadManagerDidResume:)]) {
        [self.delegate downloadManagerDidResume:task];
    }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self _startNextTask];
    });
}


- (void)addDownload:(NSString *)downloadPath targetName:(NSString *)targetName targetDir:(NSString *)targetDir {
    if (downloadPath == nil || [downloadPath length] == 0) {
        return;
    }

    DownloadTask * task = [[DownloadTask alloc] initWithDownloadPath:downloadPath targetName:targetName targetDir:targetDir];
    [self.dataSource addObject:task];
    [task saveToDB];
    [self _startNextTask];
}

- (void)addDownloadTask:(DownloadTask *)task {
    if (task.downloadPath == nil || [task.downloadPath length] == 0) {
        return;
    }
    [self.dataSource addObject:task];
    [task saveToDB];
    [self _startNextTask];
}

- (void)deleteDownloadTask:(NSString *)taskId {
    DownloadTask * task = [self _loadDownloadTask:taskId];
    if (task == nil) {
        return;
    }
    NSURLSessionDownloadTask * sessionTask = nil;
    for (NSURLSessionDownloadTask * obj in self.downloadTasks) {
        if ([task.taskId isEqualToString:obj.taskDescription]) {
            sessionTask = obj;
            break;
        }
    }
    if (sessionTask) {
        // 不保留 resume data
        [sessionTask cancel];
        [self.downloadTasks removeObject:sessionTask];
    }
    
    [task deleteToDB];
    [self.dataSource removeObject:task];
    if ([self.delegate respondsToSelector:@selector(downloadManagerDidDeleteTask:)]) {
        [self.delegate downloadManagerDidDeleteTask:task];
    }
        
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self _startNextTask];
    });
}

- (BOOL)isExistDownloadTask:(NSString *)downloadPath {
    NSUInteger count = [DownloadTask rowCountWithWhere:[NSString stringWithFormat:@"downloadPath = '%@'", downloadPath]];
    if (count == 0) {
        return NO;
    }
    return YES;
}

- (DownloadTask *)searchDownloadTask:(NSString *)downloadPath {
    return [DownloadTask searchSingleWithWhere:[NSString stringWithFormat:@"downloadPath = '%@'", downloadPath] orderBy:nil];
}

- (void)retryDownloadTask:(DownloadTask *)task {
    DownloadTask * listTask = [self _loadDownloadTask:task.taskId];
    if (listTask == nil) {
        return;
    }
    @synchronized (listTask) {
        [listTask reInit];
    }
    if ([self.delegate respondsToSelector:@selector(downloadManagerDidRetryTask:)]) {
        [self.delegate downloadManagerDidRetryTask:listTask];
    }
    
    [self _startNextTask];
}

#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    DownloadTask * task = [self _loadDownloadTask:downloadTask.taskDescription];
    
    if (task && (task.status == DownloadTaskStatusDownloading || task.status == DownloadTaskStatusWaiting)) {
        task.status = DownloadTaskStatusDownloading;
        task.info.totalBytesWritten = totalBytesWritten;
        task.info.totalBytesExpectedToWrite = totalBytesExpectedToWrite;
        
        if (task.speed.oldDate == nil) {
            task.speed.oldDate = [NSDate date];
        }
        int gap = ceil([task.speed.oldDate timeIntervalSinceNow] * -1);
        task.speed.speedBytes = bytesWritten / gap;
        
        @try {
            [task updateToDB];
        } @catch (NSException *exception) {
            NSLog(@"%@", exception);
        }
        
        if ([self.delegate respondsToSelector:@selector(downloadManagerDidChangeProgress:)]) {
            [self.delegate downloadManagerDidChangeProgress:task];
        }
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    
    DownloadTask * obj = [self _loadDownloadTask:downloadTask.taskDescription];
    if (obj) {
        BOOL result = [obj moveTmpToTarget:location];
        if (result) {
            obj.info.totalBytesWritten = obj.info.totalBytesExpectedToWrite;
            obj.status = DownloadTaskStatusFinish;
        }
        @try {
            [obj updateToDB];
        } @catch (NSException *exception) {
            NSLog(@"%@", exception);
        }
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
    DownloadTask * obj = [self _loadDownloadTask:task.taskDescription];
    if (error) {
        if ([error.userInfo.allKeys containsObject:NSURLErrorBackgroundTaskCancelledReasonKey]) {
            int cancelReson = [error.userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] intValue];
            if (cancelReson == NSURLErrorCancelledReasonUserForceQuitApplication || cancelReson == NSURLErrorCancelledReasonBackgroundUpdatesDisabled) {
                if (obj) {
                    obj.status = DownloadTaskStatusUnknown;
                    [obj updateToDB];
                }
                return;
            }
        } else if (error.code == -999) {
            // 用户点击暂停, 暂时不处理
            return;
        }
        
        NSData * resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
        NSURLSessionDownloadTask * newTask;
        if ([self _isVaildResumeData:resumeData]) {
            newTask = [session downloadTaskWithResumeData:resumeData];
        } else {
            newTask = [session downloadTaskWithRequest:task.originalRequest];
        }
        newTask.taskDescription = task.taskDescription;
        [self.downloadTasks addObject:newTask];
        [self.downloadTasks removeObject:(NSURLSessionDownloadTask *)task];
        return;
    }
    
    if (obj) {
        if ([self.delegate respondsToSelector:@selector(downloadManagerDidFinished:error:)]) {
            [self.delegate downloadManagerDidFinished:obj error:error];
        }
    }

    [self.downloadTasks removeObject:(NSURLSessionDownloadTask *)task];
    [self _startNextTask];
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (_backgroundCompletionHandler == nil) {
        return;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        self.backgroundCompletionHandler();
    });
}

#pragma mark - private
- (void)_startNextTask {
    if ([self.dataSource count] == 0) {
        return;
    }
    DownloadTask * nextTask = nil;
    for (DownloadTask * obj in self.dataSource) {
        if (obj.status <= DownloadTaskStatusMoving) {
            BOOL isContain = NO;
            for (NSURLSessionDownloadTask * task in self.downloadTasks) {
                if ([obj.taskId isEqualToString:task.taskDescription]) {
                    isContain = YES;
                    break;
                }
            }
            if (isContain == NO) {
                nextTask = obj;
                break;
            }
        }
    }
    if (nextTask == nil) {
        return;
    }
    
    [self _addToDownloadSession:nextTask];
}

- (void)_addToDownloadSession:(DownloadTask *)task {
    if ([self.downloadTasks count] >= _maxDownloadNum) {
        return;
    }

    task.status = DownloadTaskStatusWaiting;
    task.speed.oldDate = nil;
    [task updateToDB];
    
    NSData * resumeData = [task tmpResumeData];
    NSURLSessionDownloadTask * downloadTask;
    if (resumeData) {
        downloadTask = [_session downloadTaskWithResumeData:resumeData];
    } else {
        NSURL * url = [NSURL URLWithString:task.downloadPath];
        NSURLRequest * request = [NSURLRequest requestWithURL:url];
        downloadTask = [_session downloadTaskWithRequest:request];
    }
    
    if (downloadTask) {
        downloadTask.taskDescription = task.taskId;
        [self.downloadTasks addObject:downloadTask];
        [downloadTask resume];
    }
    
}

- (void)_saveResumeData:(NSData *)resumeData taskDescription:(NSString *)taskDescription {
    if ([self _isVaildResumeData:resumeData] == NO) {
        return;
    }
    DownloadTask * task;
    for (DownloadTask * obj in self.dataSource) {
        if (obj.status == DownloadTaskStatusDownloading && [obj.taskId isEqualToString:taskDescription]) {
            task = obj;
            break;
        }
    }
    if (task) {
        [task saveTmpResumeData:resumeData];
    }
}

- (DownloadTask *)_loadDownloadTask:(NSString *)taskId {
    DownloadTask * task;
    for (DownloadTask * obj in self.dataSource) {
        if ([obj.taskId isEqualToString:taskId]) {
            task = obj;
            break;
        }
    }
    return task;
}

//- (DownloadTask *)_loadDownloadTask:(NSString *)URLString status:(NSNumber *)status {
//    DownloadTask * task;
//    for (DownloadTask * obj in self.dataSource) {
//        BOOL judgeStatus = status == nil ? 1 : (obj.status == status.intValue);
//        if ([obj.downloadPath isEqualToString:URLString] && judgeStatus) {
//            task = obj;
//            break;
//        }
//    }
//    return task;
//}

- (BOOL)_isVaildResumeData:(NSData *)data {
    if (data == nil || [data length] == 0) {
        return NO;
    }
    return YES;
}

#pragma mark - getter or setter
- (NSMutableArray<DownloadTask *> *)dataSource {
    if (!_dataSource) {
        _dataSource = [DownloadTask searchWithWhere:nil];
    }
    return _dataSource;
}

- (NSMutableArray<NSURLSessionDownloadTask *> *)downloadTasks {
    if (!_downloadTasks) {
        _downloadTasks = [NSMutableArray array];
    }
    return _downloadTasks;
}

- (NSMutableArray<DownloadTask *> *)tasks {
    return [self.dataSource mutableCopy];
}

- (void)setMaxDownloadNum:(NSUInteger)maxDownloadNum {
    
    NSUInteger fixMax = maxDownloadNum;
    
    if (maxDownloadNum > 5) {
        fixMax = 5;
    } else if (maxDownloadNum <= 0) {
        fixMax = 1;
    }
    if (fixMax == _maxDownloadNum) {
        return;
    }
    NSInteger spaceNum = fixMax - _maxDownloadNum;
    _maxDownloadNum = fixMax;
    @synchronized (self) {
        if (spaceNum >= 0) {
            for (int i = 0; i < spaceNum; i ++) {
                [self _startNextTask];
            }
        } else {
            // 暂停一些
            for (int i = 0; i < -spaceNum; i ++) {
                if ([self.downloadTasks count] > _maxDownloadNum) {
                    NSURLSessionDownloadTask * last = [self.downloadTasks lastObject];
                    [last cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
                        //
                    }];
                    [self.downloadTasks removeObject:last];
                    DownloadTask * task = [self _loadDownloadTask:last.taskDescription];
                    if (task) {
                        task.status = DownloadTaskStatusUnknown;
                        [task updateToDB];
                    }
                }
            }
        }
    }
}

@end
